﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

#pragma warning disable 1591

namespace OpGen.Compiler.Generator.VB
{
    public class VBCodeWriter : CodeWriter
    {
        public VBCodeWriter(Stream stream)
            : base(stream)
        {
            UseBlockSpace = true;
            AutoNewLine = true;
        }

        public override void WriteTitleInfo(ProjectElement element)
        {
            WriteLine("' OPGEN Compiler v{1} - {0}", element.Projectname, CompilerVersion.Version());
            WriteLine("' Genreated on {0}", DateTime.Now);
            WriteLine("' --------------------------------------");
            WriteLine("");
            WriteLine("");
        }

        public override void WriteImports(ProjectElement element)
        {
            Write("Imports System");
            Write("Imports System.Collections.Generic");
            Write("Imports System.Linq");
            Write("Imports System.Text");
            Write("Imports System.IO");
            Write("");

            if (element.UseExtension)
            {
                Write("Imports OpGen.Extensions");
                Write("");
            }
        }

        public override void BeginNamespace(NamespaceElement element)
        {
            BeginBlock("Namespace {0}", element.Elementname);
        }

        public override void BeginNamespace(ProjectElement element)
        {
            BeginBlock("Namespace {0}", element.CodeEnumNamespace);
        }

        public override void EndNamespace()
        {
            EndBlock("End Namespace");
        }

        public override void BeginClass(ClassElement element, bool useExtensions)
        {
            if (useExtensions)
                BeginBlock("{0} Class {1} Implements IOpGenClass", GetModifier(element), element.Elementname);
            else
                BeginBlock("{0} Class {1}", GetModifier(element), element.Elementname);

        }

        public override void EndClass()
        {
            EndBlock("End Class");
        }

        public override void BeginEnum(EnumElement element)
        {
            BeginBlock("{0} Enum {1} As Integer", GetModifier(element), element.Elementname);
        }

        public override void BeginEnum(ProjectElement element)
        {
            BeginBlock("{0} Enum {1} : int", GetModifier(element), element.Elementname);
        }

        public override void EnumItem(EnumElement element)
        {
            foreach (EnumItemElement item in element.ChildElements)
            {
                Write("{0} = {1}\n", item.Elementname, item.Value);
            }
        }

        public override void EndEnum()
        {
            EndBlock("End Enum");
        }

        public override void BeginMessage(MessageElement element, bool useExtensions)
        {
            if (useExtensions)
                BeginBlock("{0} Class {1} Implements IOpGenMessage", GetModifier(element), element.Elementname);
            else
                BeginBlock("{0} Class {1}", GetModifier(element), element.Elementname);

            BeginOpcodeProperty(useExtensions);
            OpCodeGetterProperty(element);
            EndProperty(null);
        }

        public override void EndMessage()
        {
            EndBlock("");
        }

        public override void BeginField(FieldElement element)
        {
            if (element.Type.Type == Types.REFERENCE)
            {
                Write("Private _{1} As New {0}()", GetTypeName(element), element.Elementname);
            }
            else
            {
                Write("private _{1} As {0}", GetTypeName(element), element.Elementname);
            }
        }

        public override void EndField(FieldElement element)
        {
            return;
        }

        public override void BeginProperty(FieldElement element)
        {
            if (element.IsVirtual)
                Write("{0} Overridable Property {2} As {1}", GetModifier(element), GetTypeName(element), element.Elementname);
            else
                Write("{0} Property {2} As {1}", GetModifier(element), GetTypeName(element), element.Elementname);

            BeginBlock("");
        }

        private void BeginOpcodeProperty(bool useExtensions)
        {
            if (useExtensions)
                Write("Public Property OpCode As Integer Implements IOpGenMessage.OpCode");
            else
                Write("Public Property OpCode As Integer");

            BeginBlock("");
        }

        private void OpCodeGetterProperty(MessageElement element)
        {
            BeginBlock("Get");
            Write("Return {0}", element.Code);
            EndBlock("End Get");
        }

        public override void GetterProperty(FieldElement element)
        {
            BeginBlock("Get");
            Write("Return _{0}", element.Elementname);
            EndBlock("End Get");
        }

        public override void SetterProperty(FieldElement element)
        {
            BeginBlock("Set(value As {0})", GetTypeName(element));
            Write("_{0} = value", element.Elementname);
            EndBlock("End Set");
        }

        public override void EndProperty(FieldElement element)
        {
            EndBlock("End Property");
        }

        public override void Constructor(Element element)
        {
            #region Leerer Konstruktor
            Write("Public Sub New()");
            Write("End Sub");
            #endregion

            #region ByteArray Konstruktor
            BeginBlock("Public Sub New(ByteArray() As Byte)");


            if (element.ElementType == ElementTypes.Message)
                Write("IF BitConverter.ToInt32(ByteArray, 4) <> OpCode THEN Throw New Exception(\"Invalide OpCode\")");

            //Using Name As New Object
            BeginBlock("Using BR As New BinaryReader(New MemoryStream(ByteArray))");

            Write("ReadBR(BR)");

            EndBlock("End Using");
            EndBlock("End Sub");
            #endregion

            #region BinaryReader Konstruktor
            BeginBlock("Public Sub New(BR As BinaryReader)");

            Write("ReadBR(BR)");

            EndBlock("End Sub");
            #endregion
        }

        public override void Methods(Element element, bool useExtensions)
        {
            #region ToArray
            if (useExtensions)
                BeginBlock("Public Function ToArray() As byte() Implements IOpGenClass.ToArray");
            else
                BeginBlock("Public Function ToArray() As byte()");

            BeginBlock("Public Function ToArray() As byte()");
            BeginBlock("Using MS As New MemoryStream())");
            BeginBlock("using BW As New BinaryWriter(MS))");

            if (element.ElementType == ElementTypes.Message)
                Write("BW.Write(OpCode)");

            foreach (FieldElement Field in element.ChildElements)
            {
                if (Field.Type.Type == Types.REFERENCE)
                {
                    if (Field.Type.IsList)
                    {
                        BeginBlock("For i As Interger = 1 To _{0}.Count", Field.Elementname);
                        Write(GetBinaryWriterMethodeName("BW", Field, "i"));
                        EndBlock("Next");

                    }
                    else
                    {
                        Write(GetBinaryWriterMethodeName("BW", Field, ""));
                    }
                }
                else
                {
                    if (Field.Type.IsList)
                    {
                        BeginBlock("For i As Interger = 1 To _{0}.Count", Field.Elementname);
                        Write(GetBinaryWriterMethodeName("BW", Field, "i"));
                        EndBlock("Next");
                    }
                    else
                    {
                        Write(GetBinaryWriterMethodeName("BW", Field, ""));
                    }
                }
            }

            Write("BW.Flush()");
            Write("Return MS.ToArray()");

            EndBlock("End Using");
            EndBlock("End Using");
            EndBlock("End Function");
            #endregion

            #region ReadBR
            BeginBlock("Public Sub ReadBR(BR As BinaryReader)");

            Write("BR.ReadInt32()  ' OpCode Dummy");

            foreach (FieldElement Field in element.ChildElements)
            {
                if (Field.Type.Type == Types.REFERENCE)
                {
                    if (Field.Type.ReferenceElement.ElementType == ElementTypes.Enum)
                    {
                        Write("_{0} = BR.ReadInt32();", Field.Elementname, Field.Type.ReferenceElement.Fullname);
                    }
                    else
                    {
                        if (Field.Type.IsList)
                        {
                            BeginBlock("For i As Integer = 1 To BR.ReadInt32()");
                            Write("_{0}.Add({1});", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                            EndBlock("Next");

                        }
                        else
                        {
                            Write("_{0} = {1};", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                        }
                    }
                }
                else
                {
                    if (Field.Type.IsList)
                    {
                        BeginBlock("For i As Integer = 1 To BR.ReadInt32()");
                        Write("_{0}.Add({1});", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                        EndBlock("Next");

                    }
                    else
                    {
                        Write("_{0} = {1};", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                    }
                }
            }

            EndBlock("End Sub");
            #endregion
        }

        protected override string GetModifier(Element element)
        {
            switch (element.Modifier)
            {
                case Modifiers.None:
                    return "";
                case Modifiers.Unkown:
                    return "Public";
                case Modifiers.Private:
                    return "Private";
                case Modifiers.Public:
                    return "Public";
                case Modifiers.Internal:
                    return "Friend";
                case Modifiers.Protected:
                    return "Protected";
                default:
                    return "";
            }
        }

        protected override string GetBinaryReaderMethodeName(string BO, string BA, FieldElement Field)
        {
            switch (Field.Type.Type)
            {
                case Types.INT16:
                    return string.Format("{0}.ReadInt16()", BO, BA);
                case Types.INT:
                case Types.INT32:
                    return string.Format("{0}.ReadInt32()", BO, BA);
                case Types.INT64:
                case Types.LONG:
                    return string.Format("{0}.ReadInt64()", BO, BA);
                case Types.FLOAT:
                    return string.Format("{0}.ReadSingle()", BO, BA);
                case Types.DOUBLE:
                    return string.Format("{0}.ReadDouble()", BO, BA);
                case Types.DECIMAL:
                    return string.Format("{0}.ReadDecimal()", BO, BA);
                case Types.STRING:
                    return string.Format("{0}.ReadString()", BO, BA);
                case Types.BYTE:
                    return string.Format("{0}.ReadByte()", BO, BA);
                case Types.BOOL:
                    return string.Format("{0}.ReadBoolean()", BO, BA);
                case Types.CHAR:
                    return string.Format("{0}.ReadChar()", BO, BA);
                case Types.DATETIME:
                    return string.Format("Date.FromBinary({0}.ReadInt64())", BO, BA);
                case Types.REFERENCE:
                    return string.Format("New {0}({1})", Field.ParentElement.Elementname, BA);
                case Types.NONE:
                default:
                    return string.Format("{0}.ReadString()", BO, BA);
            }
        }

        protected override string GetBinaryWriterMethodeName(string BO, FieldElement Field, string IndexerChar)
        {
            var Indexer = "";

            if (Field.Type.IsList)
                Indexer = "(" + IndexerChar + ")";

            switch (Field.Type.Type)
            {
                case Types.INT16:
                case Types.INT:
                case Types.INT32:
                case Types.INT64:
                case Types.LONG:
                case Types.FLOAT:
                case Types.DOUBLE:
                case Types.DECIMAL:
                case Types.STRING:
                case Types.BYTE:
                case Types.BOOL:
                case Types.CHAR:
                    return string.Format("{0}.Write(_{1}{2})", BO, Field.Elementname, Indexer);
                case Types.DATETIME:
                    return string.Format("{0}.Write(_{1}{2}.ToBinary())", BO, Field.Elementname, Indexer);
                case Types.REFERENCE:
                    return string.Format("{0}.Write(_{1}{2}.ToArray())", BO, Field.Elementname, Indexer);
                case Types.NONE:
                default:
                    return string.Format("{0}.Write(_{1}{2})", BO, Field.Elementname, Indexer);
            }
        }

        protected override string GetTypeName(Element element)
        {
            var Ret = "";

            switch (element.Type.Type)
            {
                case Types.INT16:
                    Ret = "Int16";
                    break;
                case Types.INT:
                case Types.INT32:
                    Ret = "Integer";
                    break;
                case Types.INT64:
                case Types.LONG:
                    Ret = "Long";
                    break;
                case Types.FLOAT:
                    Ret = "Single";
                    break;
                case Types.DOUBLE:
                    Ret = "Double";
                    break;
                case Types.DECIMAL:
                    Ret = "Decimal";
                    break;
                case Types.STRING:
                    Ret = "String";
                    break;
                case Types.BYTE:
                    Ret = "Byte";
                    break;
                case Types.BOOL:
                    Ret = "Bool";
                    break;
                case Types.CHAR:
                    Ret = "Char";
                    break;
                case Types.DATETIME:
                    Ret = "Date";
                    break;
                case Types.REFERENCE:
                    Ret = element.Type.ReferenceElement.Fullname;
                    break;
                case Types.NONE:
                    break;
                default:
                    break;
            }

            if (element.Type.IsList)
                Ret = string.Format("List(Of {0})", Ret);

            return Ret;
        }
    }
}
